import React from 'react';
import ReactDOM from 'react-dom';
import {
  RecoilRoot,
  useRecoilState,
  useResetRecoilState,
  useRecoilValue,
  useSetRecoilState,
  useRecoilValueLoadable,
  useSetRecoil,
  useRecoilStateLoadable,
  useRecoilCallback,
  atom,
  selector,
} from 'recoil';

// useRecoilState useRecoilValue useSetRecoil 三个api的返回值如下
// const [name, setName] = useRecoilState(nameAtom)
// const name = useRecoilValue(nameAtom)
// const setName = useSetRecoil(nameAtom)

// 读 Recoil state：useRecoilValue、useRecoilValueLoadable
// 写 Recoil state：useSetRecoilState、useResetRecoilState
// 读和写 Recoil state：useRecoilState、useRecoilStateLoadable、useRecoilCallback
// 其中 useRecoilState = useRecoilValue + useSetRecoilState，后两个就是获取 Recoil state 的只读状态值和设置状态值。
// useResetRecoilState 用于重置 Recoil state 为初始状态，也就是 default 值。
// useRecoilStateLoadable 用于获取异步状态和设置状态，useRecoilValueLoadable 只读异步状态
// useRecoilCallback ：上面的几个 api 都会让组件去订阅 state，当 state 改变组件就会更新，但是使用 useRecoilCallback   可以不用更新组件。 useRecoilCallback  允许组件不订阅 Recoil state 的情况下获取和设置 Recoil state，也就说组件可以获取 state 但是不用更新，反过来组件不必更新自身才能获取新 state，将读取 state 延迟到我们需要的时候而不是在组件 monted 的时候。用法：

// 一个 atom 代表一个状态。Atom 可在任意组件中进行读写。读取 atom 值的组件隐式订阅了该 atom，
// 因此任何 atom 的更新都将导致订阅该 atom 的组件重新渲染
const nameAtom = atom({
  key: 'name',
  default: '小月',
});

const ageAtom = atom({
  key: 'age',
  default: 18,
});

// ​selector 代表一个派生状态，派生状态是状态的转换。
// 你可以将派生状态视为将状态传递给以某种方式修改给定状态的纯函数的输出
const ageSelector = selector({
  key: 'ageSelector', // 全局下保持唯一性
  get: ({ get }) => {
    const ageState = get(ageAtom);
    return ageState;
  },
});

const asyncAgeSelector = selector({
  key: 'asyncAgeSelector', // 全局下保持唯一性
  get: async ({ get }) => {
    const ageState = await get(ageAtom);
    return ageState;
  },
});

const NameCmp = () => {
  console.log('name component render');
  const [name] = useRecoilState(nameAtom);
  return <div>my name is {name}</div>;
};

const AgeCmp = () => {
  console.log('age component render');
  // const [age] = useRecoilState(ageAtom);
  const age = useRecoilValue(ageSelector);
  return <div>my age is {age}</div>;
};

function AsyncAgeCmp() {
  // 移步读取数据和同步不一样
  const ageLoadable = useRecoilValueLoadable(asyncAgeSelector);
  console.log('移步获取数据: ', ageLoadable);

  switch (ageLoadable.state) {
    case 'hasValue':
      return <div>async：{ageLoadable.contents}</div>;
    case 'loading':
      return <div>Loading...</div>;
    case 'hasError':
      throw ageLoadable.contents;
  }
}

const ControlCmp = () => {
  console.log('control component render');
  const [, setName] = useRecoilState(nameAtom);
  const [, setAge] = useRecoilState(ageAtom);
  // 当我们只需要更新，不需要订阅时，可以使用该方法。状态更新时，该组件则不会重新渲染。
  // const setAge = useSetRecoilState(ageAtom);

  const onChangeNameClick = () => {
    setName(`leo${Date.now()}`);
  };

  const onChangeAgeClick = () => {
    setAge((prev) => prev + 1);
  };

  const sleep = (wait) => {
    return new Promise((r) => setTimeout(r, wait));
  };

  const asyncChangeAge = async () => {
    await sleep(1000);
    onChangeAgeClick();
  };

  return (
    <div>
      <button onClick={onChangeNameClick}>change name</button>
      <button onClick={onChangeAgeClick}>add age</button>
      <button onClick={asyncChangeAge}>async add age</button>
    </div>
  );
};

function App() {
  return (
    <RecoilRoot>
      <React.Suspense fallback={<div>loading...</div>}>
        <NameCmp />
        <AgeCmp />
        <AsyncAgeCmp />
        <ControlCmp />
      </React.Suspense>
    </RecoilRoot>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));
